package com.amos.doublewriterconsistence.web;

import com.amos.doublewriterconsistence.bean.Result;
import com.amos.doublewriterconsistence.entity.Inventory;
import com.amos.doublewriterconsistence.request.Request;
import com.amos.doublewriterconsistence.request.impl.InventoryCacheRequest;
import com.amos.doublewriterconsistence.request.impl.InventoryDBRequest;
import com.amos.doublewriterconsistence.service.InventoryService;
import com.amos.doublewriterconsistence.service.RequestAsyncProcessService;
import com.amos.doublewriterconsistence.util.ResultWapper;
import org.omg.PortableInterceptor.SYSTEM_EXCEPTION;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * Copyright © 2020 五月工作室. All rights reserved.
 *
 * @Project: double-writer-consistence
 * @ClassName: InventoryController
 * @Package: com.amos.doublewriterconsistence.web
 * @author: amos
 * @Description: 主要测试
 * 1. 所有的请求是否从缓存队列中走
 * 2. 通过延迟数据的操作，看看读请求是否有等待
 * 3，读请求通过之后，相同的读请求是否直接返回
 * 4. 读请求的数据是否从缓存中获取
 * @date: 2020/2/17 0019 下午 15:31
 * @Version: V1.0
 */
@RestController
@RequestMapping(value = "/inventory")
public class InventoryController {
    public final Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * 库存的操作方法(统一提供库存的方法: 从数据库中查询，更新数据库，从缓存中查询，删除缓存数据，保存缓存数据)
     */
    @Autowired
    InventoryService inventoryService;

    /**
     *所有的请求我们都需要打入到缓存队列中来执行下，所以同一库存，我们需要他打入到同一缓存队列中进行处理，如何来实现这个这功能呢？
     * 这里我们使用hash值取模的方式来实现
     */
    @Autowired
    RequestAsyncProcessService requestAsyncProcessService;

    /**
     * 更新库存的数据记录
     * 1. 将更新数据的记录路由到指定的队列中
     * 2. 后台不断的将从队列中取值去处理
     *
     * @param inventory
     * @return
     */
    @PostMapping(value = "/updateInventory")
    public Result updateInventory(@RequestBody Inventory inventory) {
        // 为了简单起见，我们就不用log4j那种日志框架去打印日志了
        // 其实log4j也很简单，实际企业中都是用log4j去打印日志的，自己百度一下
        System.out.println("===========日志===========: 接收到更新商品库存的请求，商品id=" + inventory.getId() + ", 商品库存数量=" + inventory.getCount());

        try {
            Request request = new InventoryDBRequest(inventory, this.inventoryService);
            this.requestAsyncProcessService.route(request);
            return ResultWapper.success();
        } catch (Exception e) {
            if (this.logger.isDebugEnabled()) {
                e.printStackTrace();
            }
            this.logger.error(e.getMessage());
            System.out.println("/////////////////////////////////////"+e.getMessage());
            return ResultWapper.error(e.getMessage());
        }
    }

    /**
     * 获取库存记录
     * 如果在在一定时间内获取不到数据，则直接从数据库中获取，并且数据写入到缓存中
     *
     * @param id
     * @return
     */
    @GetMapping(value = "/getInventory/{id}")
    public Result getInventory(@PathVariable("id") String id) {
        System.out.println("===========日志===========: 接收到一个商品库存的读请求，商品id=" + id);
        this.logger.info("获取库存记录：{}", id);
        Inventory inventory = null;
        try {

            /**
             *
             * （一）、接口类型的引用变量request 去接收InventoryCacheRequest对象地址，只能调用接口中的方法
             *
             *  InventoryCacheRequest实现了读取数据的代码逻辑
             * 即：一个读请求过来，我们需要从数据库中读取对应的缓存记录，并且将该数据保存到缓存中， 由于我们需要将所有的请求都是通过缓存队列来处理的，
             * 所以缓存的操作类应该实现Request接口，在定义好的方法中实现缓存读取的操作
             * 缓存读取的操作类中添加了isForceFresh字段来为过滤多重读请求提供支持
             */
            Request request = new InventoryCacheRequest(id, this.inventoryService, Boolean.FALSE);

            /**
             * 做请求的路由，根据每个请求的商品id，路由到对应的内存队列中去
             */
            this.requestAsyncProcessService.route(request);

            /**
             *  将请求扔给service异步去处理以后，就需要while(true)一会儿，在这里hang住
             * 	尝试等待前面有商品库存更新的操作，同时缓存刷新的操作，将最新的数据刷新到缓存中
             * 在java中是最常用的获取系统时间的方法,它返回的是1970年1月1日0点到现在经过的毫秒数
             */
            long startTime = System.currentTimeMillis();

            long waitTime = 0L;
            long endTime = 0L;

            // 不断循环从缓存中获取数据
            // 如果在在一定时间（3秒内）内获取不到数据，则直接从数据库中获取，并且数据写入到缓存中
            while (true) {
                //（3000毫秒也就是3秒）
               // if (waitTime > 3000) {
               //     break;
               // }
                // 一般公司里面，面向用户的读请求控制在200ms就可以了
                if(waitTime > 200) {
                    break;
                }

                /**
                 * 尝试去redis中读取一次商品库存的缓存数据
                 *
                 *
                 * 当从缓存中读取不到数据，则将该读请求写入缓存队列中，此时一定要注意读请求超时的问题，
                 * 系统设计要做到每个读请求都在必须要在超时时间内完返回请求结果。
                 */
                inventory = this.inventoryService.getInventoryCache(id);

                if (null != inventory) {
                    System.out.println("===========日志===========: 在200ms内读取到了redis中的库存缓存，商品id=" + inventory.getId() + ", 商品库存数量=" + inventory.getCount());
                    this.logger.info("从缓存中获取到数据");
                    return ResultWapper.success(inventory);
                } else {
                    // 如果没有读取到结果，那么等待一段时间
                    //将当前线程休眠2秒
                    Thread.sleep(20);
                    endTime = System.currentTimeMillis();
                    //为获取执行时间
                    waitTime = System.currentTimeMillis() - startTime;
                }

            }
            // 直接从数据库中获取数据
            inventory = this.inventoryService.selectById(id);
            if (null != inventory) {
                //// 将缓存刷新一下
                //发现缓存中没有数据，同样会从数据库中读取数据，并且存入到缓存中
                request = new InventoryCacheRequest(id, this.inventoryService, Boolean.TRUE);
                this.requestAsyncProcessService.route(request);
                return ResultWapper.success(inventory);
            }
            return ResultWapper.error("查询不到数据");
        } catch (Exception e) {
            if (this.logger.isDebugEnabled()) {
                e.printStackTrace();
            }
            this.logger.error(e.getMessage());
            return ResultWapper.error(e.getMessage());
        }
    }
}